/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.yonsei.iwb.smarttv.querygraph;

import com.hp.hpl.jena.graph.Node;
import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.sparql.expr.E_Regex;
import com.hp.hpl.jena.sparql.expr.E_Str;
import com.hp.hpl.jena.sparql.expr.Expr;
import com.hp.hpl.jena.sparql.expr.ExprVar;
import com.hp.hpl.jena.sparql.syntax.ElementFilter;
import com.hp.hpl.jena.sparql.syntax.ElementGroup;
import com.hp.hpl.jena.sparql.syntax.ElementTriplesBlock;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;
import edu.yonsei.iwb.smarttv.model.*;
import edu.yonsei.iwb.smarttv.querygraph.algorithm.*;
import edu.yonsei.iwb.smarttv.result.*;
import edu.yonsei.iwb.smarttv.util.SMARTTVLog;
import edu.yonsei.iwb.smarttv.util.UtilMethodSet;
import java.util.*;

/**
 *
 * @author JDK
 */
public class DynamicSPARQLGenerator {

    private final Resource TARGETRESOURCE = SMARTTVModel.TARGET_CLASS_URI;
    private Resource TARGETRESOURCE_FROM_QUERY;
    private Model model;
    private Vector<MNode> nodes;
    private Hashtable<Resource, Integer> nodeInfo;
    private MatrixModel matrixModel;
    private double[][] weightInfo;
//    private Resource[][] propertyMap;
    private AStarAlgorithm asa;
    private int v_num;
    private SMARTTVModel tvModel;
    private Vector<ConceptualGraph> conceptualGraphSet;
    private Vector<ConceptualGraph> stForAllCGSet;

    public DynamicSPARQLGenerator(SMARTTVModel model) {
        this.tvModel = model;
        this.model = model.getJenaModel();
        this.matrixModel = model.getMatrixModel();
        this.nodes = this.matrixModel.getNodes();
        this.nodeInfo = this.matrixModel.getNodeInfo();
        this.weightInfo = this.matrixModel.getWeightInfo();
//        this.propertyMap = this.matrixModel.getPropertyMap();
        this.asa = new AStarAlgorithm(model);
        this.conceptualGraphSet = new Vector<ConceptualGraph>();
        this.stForAllCGSet = new Vector<ConceptualGraph>();
    }

    /**
     * 사용자 질의문의 개체 식별 결과를 바탕으로 동적으로 SPARQL을 생성함
     *
     * @param ner 개체 식별 결과
     * @return SPARQL Query 집합
     */
    public ShortestPathVectorCandidateSet dynamicSPARQLGenerator(NERResults ner) {
        this.initializeNodeNumber();

        GenerateSpanningTreeAlgorithm gsta = new GenerateSpanningTreeAlgorithm();
        ShortestPathVectorCandidateSet candidateSP = new ShortestPathVectorCandidateSet();
        QueryCombinationSet combinationQuerySet = this.makeQueryCombinationSet(ner);

        for (int i = 0; i < combinationQuerySet.size(); i++) {
            QueryCombination qc = combinationQuerySet.get(i);
            this.checkMediaClassExist(qc);
            
            SMARTTVLog.log(SMARTTVLog.DETAIL, "Target Resource is " + this.TARGETRESOURCE_FROM_QUERY.getLocalName()+"\n");
            
            ConceptualGraph cg = new ConceptualGraph(qc);
            String cgID = "CG" + (i + 1);
            cg.setCGID(cgID);
            this.conceptualGraphSet.add(cg);

            if (cg.isEmpty()) {
                // 쿼리가 키워드 하나로만 이루어져 있을경우
                ShortestPathVector spv = new ShortestPathVector();
                ResourceNode rn = qc.firstElement();
                ConceptArc ca = new ConceptArc();
                ShortestPath sp = new ShortestPath();
                Vector<Resource> classname = new Vector<Resource>();
                Vector<Node> variable = new Vector<Node>();
                classname.add(rn.getNode());
                variable.add(rn.getVariableNode());
                VariableMap vm = new VariableMap(classname, variable);
                sp.setVariableMap(vm);
                spv.put(ca, sp);
                spv.setTargetURI(rn.getNode());
                spv.setScore(this.calculateShortestPathVectorScore(spv));
                candidateSP.add(spv);
            } else {
                Vector<ShortestPathVector> spvset = this.asa.getShortestPathVector(cg);
                Vector<ConceptualGraph> spanningTreeSet = gsta.generateSpanningTree(cg);  //이부분에 새로운 Conceptual Tree 생성하는 메소드 추가
                
                //////////////////////// Conceptual Tree 형태로 변경
                
//                LinkedHashMap<ResourceNode, Vector<ConceptArc>> conceptArcTable = this.generateConceptArcSetOfQuery(qc);
//                Vector<ConceptualGraph> conceptualTreeSet = this.generateConceptualTree(conceptArcTable, qc); // Conceptual Tree 생성하는 메소드 추가
                
                /////////////////////////////////////////////////////////////////////////////////
                
                for (int j = 0; j < spvset.size(); j++) {
                    ShortestPathVector spv = spvset.get(j);
                    spv.setVariablesOfShortestPath(this.v_num);
                    this.v_num = spv.getVariableSeq();
                    if (!spv.isEmpty()) {
                        for (int k = 0; k < spanningTreeSet.size(); k++) {
                            ConceptualGraph st = spanningTreeSet.get(k);
                            this.stForAllCGSet.add(st);

                            ShortestPathVector subspv = spv.getConceptArcMatchedSPV(st);
                            if (subspv.size() == st.size()) {
                                int spIDNum = 1;
                                //target resource 입력
                                subspv.setTargetURI(this.TARGETRESOURCE_FROM_QUERY);
                                subspv.setScore(this.calculateShortestPathVectorScore(subspv));
                                subspv.setSPID(st.getCGID() + "-" + "SP" + spIDNum);
                                candidateSP.add(subspv);
                                spIDNum++;
                            }
                        }
                    }
                }
            }
        }
        candidateSP.orderByScore();
//        SMARTTVLog.log(SMARTTVLog.DETAIL, "Shortest Path Vector of All Candidate Spanning Tree: \n" + candidateSP.toString());
        return candidateSP;
    }
    
    private LinkedHashMap<ResourceNode, Vector<ConceptArc>> generateConceptArcSetOfQuery(QueryCombination qc){
        
        LinkedHashMap<ResourceNode, Vector<ConceptArc>> result = new LinkedHashMap<ResourceNode, Vector<ConceptArc>>();
        
        for (int i = 0; i < qc.size() - 1; i++) {
            Vector<ConceptArc> arcset = new Vector<ConceptArc>();
            for (int j = i + 1; j < qc.size(); j++) {
                ResourceNode source = qc.get(i);
                ResourceNode target = qc.get(j);
                ConceptArc sub_cg;
                if(qc.get(i).getType() == NodeType.LITERAL_TYPE){
                    if (qc.get(j).getType() != NodeType.PROPERTY_TYPE && qc.get(j).getType() != NodeType.LITERAL_TYPE && qc.get(j).getType() != NodeType.INSTANCE_TYPE) {
                        
                        // LITERAL의 경우 Property 고려 안하고 있음 - 20120715
                        sub_cg = new ConceptArc(source, target, new WeightFactory(1), ArcType.LITERAL_TYPE);
                        arcset.add(sub_cg);
//                        break; // Literal value의 바로 옆에 있는 애만 아크로 생성
                    }
                }else if ((qc.get(i).getType() == NodeType.CLASS_TYPE || qc.get(i).getType() == NodeType.INSTANCE_TYPE) && (qc.get(j).getType() == NodeType.CLASS_TYPE || qc.get(j).getType() == NodeType.INSTANCE_TYPE)) {
                    if(qc.get(i).getType() == NodeType.INSTANCE_TYPE && qc.get(j).getType() == NodeType.INSTANCE_TYPE){
                        continue;
                    }else{
                        sub_cg = new ConceptArc(source, target, new WeightFactory(1));
                        for (int k = i + 1; k < j; k++) {
                            
                            //Arc에 프로퍼티 입력 다시 해야함
                            if (qc.get(k).getType() == NodeType.PROPERTY_TYPE) {
                                Resource property = qc.get(k).getNode();
                                sub_cg.setProperty(ResourceFactory.createProperty(property.getURI()));
                            }
                        }
                        arcset.add(sub_cg);
                    }
                }
            }
            
            
            
        }
        
        return result;
    }
       
    
    private Vector<ConceptualGraph> generateConceptualTree(LinkedHashMap<ResourceNode, Vector<ConceptArc>> catable, QueryCombination qc){
        Vector<ConceptualGraph> result = new Vector<ConceptualGraph>();
        
        Set<ResourceNode> keyset = catable.keySet();
        Iterator<ResourceNode> it = keyset.iterator();
        while(it.hasNext()){
            ResourceNode rn = it.next();
            Vector<ConceptArc> caset = catable.get(rn);
            Vector<ConceptualGraph> tempResult = new Vector<ConceptualGraph>();
            for (int i = 0; i < caset.size(); i++) {
                ConceptualGraph cg = new ConceptualGraph();
                ConceptArc ca = caset.get(i);
                cg.add(ca);
                tempResult.add(cg);
            }
            this.updateConceptualTree(result, tempResult);
        }
        
        return result;
    }
    
    private void updateConceptualTree(Vector<ConceptualGraph> oldcgSet, Vector<ConceptualGraph> tempcgSet){
        if(oldcgSet.isEmpty()){
            for (int i = 0; i < tempcgSet.size(); i++) {
                oldcgSet.add(tempcgSet.get(i));
            }
        }else{
            Vector<ConceptualGraph> newResultSet = new Vector<ConceptualGraph>();
            for (int i = 0; i < oldcgSet.size(); i++) {
                ConceptualGraph oldcg = oldcgSet.get(i);
                for (int j = 0; j < tempcgSet.size(); j++) {
                    ConceptualGraph tmpcg = tempcgSet.get(j);
                                                            
                    ConceptualGraph newcg = new ConceptualGraph();
                    newcg.updateConceptualTree(oldcg);
                    newcg.updateConceptualTree(tmpcg);
                    newResultSet.add(newcg);
                }
            }

            oldcgSet.clear();
            oldcgSet.addAll(newResultSet);
        }
    }
    
    private boolean isProperConceptArc(ConceptualGraph existcg, ConceptArc tmpca, QueryCombination qc){
        
        return false;
    }

    private void checkMediaClassExist(QueryCombination qc) {
        for (int j = 0; j < qc.size(); j++) {
            ResourceNode rn = qc.get(j);
            Resource r = qc.get(j).getNode();
            StmtIterator stmt = this.model.listStatements(rn.getNode(), RDFS.subClassOf, TARGETRESOURCE);
            if (stmt.hasNext() || rn.getNode().equals(this.TARGETRESOURCE)) {
                if (rn.getType() == NodeType.CLASS_TYPE) {
                    this.TARGETRESOURCE_FROM_QUERY = r;                    
                    return;
                }
            }
        }
        ResourceNode media = new ResourceNode(nodes.get(nodeInfo.get(TARGETRESOURCE)));
        media.setVariableNode(Node.createVariable(UtilMethodSet.getVariableCharacter(this.v_num++)));
        qc.add(media);
        this.TARGETRESOURCE_FROM_QUERY = this.TARGETRESOURCE;        
    }

    private boolean checkTargetClassExist(QueryCombination qc) {

        ResourceNode rn = qc.lastElement();
        Resource r = rn.getNode();

        if (rn.getType() == NodeType.CLASS_TYPE) {
//            SMARTTVLog.log(SMARTTVLog.DETAIL, "Target Resource is : " + r.getLocalName()+"\n");
            this.TARGETRESOURCE_FROM_QUERY = r;
//            spv.setTargetURI(r);
            return true;
        } else {
            if (TARGETRESOURCE == null) {
//                SMARTTVLog.log(SMARTTVLog.DETAIL, "Predefined Target resource is not exist\n");
                return false;
            } else {
                ResourceNode targetClass = new ResourceNode(nodes.get(nodeInfo.get(TARGETRESOURCE)));
                targetClass.setVariableNode(Node.createVariable(UtilMethodSet.getVariableCharacter(this.v_num++)));
                qc.add(targetClass);
//                spv.setTargetURI(TARGETRESOURCE);
                this.TARGETRESOURCE_FROM_QUERY = this.TARGETRESOURCE;
//                SMARTTVLog.log(SMARTTVLog.DETAIL, "Target Resource is : " + TARGET_RESOURCE.getLocalName()+"\n");
                return true;
            }
        }
    }

    private QueryCombinationSet makeQueryCombinationSet(NERResults ner) {
        QueryCombinationSet qcs = new QueryCombinationSet();
        Iterator<String> keyIterator = ner.getNamedEntityResults().keySet().iterator();
        while (keyIterator.hasNext()) {
            boolean isLiteral = true;
            String key = keyIterator.next();
            Vector<ResourceNode> types = new Vector<ResourceNode>();
            NamedEntitySet nes = ner.getNamedEntitySetByString(key);
            for (int i = 0; i < nes.size(); i++) {
                NamedEntity ne = nes.get(i);
                ResultTypeInfo rti = ne.getResultTypes();
                for (int j = 0; j < rti.size(); j++) {
                    ResultNode resultNode = rti.get(j);
                    ResourceNode r = null;
                    if (resultNode.equals(OWL.ObjectProperty) || resultNode.equals(OWL.DatatypeProperty)) {
                        // 맵핑 결과가 Property
                        r = new ResourceNode(ne.getNamedEntityNode().getResultNode(), NodeType.PROPERTY_TYPE);

                    } else {
                        if (resultNode.equals(OWL.Class) || resultNode.equals(RDFS.Class)) {
                            // 맵핑 결과가 Class                            
                            r = new ResourceNode(nodes.get(nodeInfo.get(ne.getNamedEntityNode().getResultNode())));
                            r.setVariableNode(Node.createVariable(UtilMethodSet.getVariableCharacter(this.v_num++)));
                        } else if (resultNode.getLabel().equals("LiteralValue")) {
                            //맵핑 결과가 Literal
                            if (isLiteral) {
                                r = new ResourceNode(ResourceFactory.createResource(key), NodeType.LITERAL_TYPE);
                                r.setVariableNode(Node.createVariable(UtilMethodSet.getVariableCharacter(this.v_num++)));
                                isLiteral = false;
                            }
                        } else {
                            // 맵핑 결과가 Instance
                            System.out.println("맵핑 결과가 인스턴스임");
                            r = new ResourceNode(nodes.get(nodeInfo.get(resultNode.getResultNode())), ne.getNamedEntityNode().getResultNode());
                        }
                    }
                    if (!types.contains(r) && r != null) {
                        types.add(r);
                    }
                }
            }
            qcs = this.makeCombination(qcs, types);
        }
        SMARTTVLog.log(SMARTTVLog.DETAIL, "Query Combination of Best NER Result: \n" + qcs.toString());
        return qcs;
    }

    private QueryCombinationSet makeCombination(QueryCombinationSet qcs, Vector<ResourceNode> types) {
        QueryCombinationSet result = new QueryCombinationSet();
        if (qcs.size() == 0) {
            for (int j = 0; j < types.size(); j++) {
                QueryCombination qc = new QueryCombination();
                ResourceNode rn = types.get(j);
                qc.add(rn);
                result.add(qc);
            }
        } else if (types.isEmpty()) {
            return qcs;
        } else {
            for (int i = 0; i < qcs.size(); i++) {
                QueryCombination qc = qcs.get(i);
                for (int j = 0; j < types.size(); j++) {
                    QueryCombination newQc = new QueryCombination(qc);
                    ResourceNode rn = types.get(j);
                    newQc.add(rn);
                    result.add(newQc);
                }
            }
        }
        return result;
    }

    public Query generateSPARQL(ShortestPathVector spv) {
        Query query = new Query();
        Vector<Triple>  typeTriple =  new Vector<Triple>();
        Vector<Triple>  generalTriple =  new Vector<Triple>();
        
        ElementTriplesBlock etb = new ElementTriplesBlock();
        ElementFilter filter;
        ElementGroup eg = new ElementGroup();
        Vector<Node> typeDefinedNodeList = new Vector();
        Enumeration<ConceptArc> e = spv.keys();
        while (e.hasMoreElements()) {
            ConceptArc ca = e.nextElement();
            ShortestPath sp = spv.get(ca);
            VariableMap variableMap = sp.getVariableMap();
            SMARTTVLog.log(SMARTTVLog.DETAIL, "Variable Mapping Result:\n" + variableMap.toString());
            
//            if(ca.getArcType() == ArcType.LITERAL_TYPE){
//                // Literal이 입력됐을 경우 처리
//                Resource prev = ca.getHead().getNode();
//                
//                Node[] nodeSet = variableMap.getVarMapByIndex().get(0);
//                
//                Node l = nodeSet[0];
//                Node r = nodeSet[1];
//                
////                generalTriple.add(new Triple(r, Node.createVariable(UtilMethodSet.getVariableCharacter(this.v_num++)), l));
//                etb.addTriple(new Triple(r, Node.createVariable(UtilMethodSet.getVariableCharacter(this.v_num++)), l));
//                Expr e1 = new E_Regex(new E_Str(new ExprVar(l)), prev.toString(),"i");
//                filter = new ElementFilter(e1);
//                eg.addElement(filter);
//            }
//            else{
                
                for (int i = 0; i < sp.size(); i++) {
                    MArc arc = sp.get(i);
                    Resource head = arc.getHeadNode().getNode();
                    Resource tail = arc.getTailNode().getNode();
                                        
                    Node[] nodeSet = variableMap.getVarMapByIndex().get(i);
                    Node headVar = nodeSet[0];
                    Node tailVar = nodeSet[1];
                    
                    // variable type define                                        
                    if (!typeDefinedNodeList.contains(headVar) && head.toString().startsWith("http")) {
                        if(headVar.isVariable()){
                            typeTriple.add(new Triple(headVar, RDF.type.asNode(), head.asNode()));    
                            typeDefinedNodeList.add(headVar);
                        }                        
                    }
                    
                    if(i == sp.size()-1){
                        if (!typeDefinedNodeList.contains(tailVar) && tail.toString().startsWith("http")) {
                            if(tailVar.isVariable()){
                                typeTriple.add(new Triple(tailVar, RDF.type.asNode(), tail.asNode()));    
                                typeDefinedNodeList.add(tailVar);
                            }
                        }
                    }                    
                    
                    
                    
                    if(ca.getArcType() == ArcType.LITERAL_TYPE){
                        // Literal이 입력됐을 경우 처리
                        Resource prev = ca.getHead().getNode();

                        Node[] nodeSet2 = variableMap.getVarMapByIndex().get(0);

                        Node l = nodeSet2[0];
                        Node r = nodeSet2[1];

        //                generalTriple.add(new Triple(r, Node.createVariable(UtilMethodSet.getVariableCharacter(this.v_num++)), l));
                        etb.addTriple(new Triple(r, Node.createVariable(UtilMethodSet.getVariableCharacter(this.v_num++)), l));
                        Expr e1 = new E_Regex(new E_Str(new ExprVar(l)), prev.toString(),"i");
                        filter = new ElementFilter(e1);
                        eg.addElement(filter);
                    }else{
                        // variable realtion define
                        Node propertyVar = arc.getProperty().asNode();
                        if(arc.getDirection()){
                            generalTriple.add(new Triple(headVar, propertyVar, tailVar));
                        }else{
                            generalTriple.add(new Triple(tailVar, propertyVar, headVar));
                        }
                    }
                    
                    
                    
                }
//            }                        
        }

        Vector<Node> select = spv.getAllVariablesByClass(spv.getTargetURI());
        for (int i = 0; i < select.size(); i++) {
            Node selectVar = select.get(i);
            if (selectVar.isVariable()) {
                query.setQuerySelectType();
                query.addResultVar(selectVar);
                break;
            }
        }
        
//        this.orderByResourceWeight(generalTriple);
        for (int i = 0; i < generalTriple.size(); i++) {
            etb.addTriple(generalTriple.get(i));
        }
        
//        this.orderByResourceWeight(typeTriple);
        for (int i = 0; i < typeTriple.size(); i++) {
            etb.addTriple(typeTriple.get(i));            
        }
                
        eg.addElement(etb);
        query.setQueryPattern(eg);        
        SMARTTVLog.log(SMARTTVLog.DETAIL, "Generated SPARQL:\n" + query.serialize());
        return query;
    }

    private void orderByResourceWeight(Vector<Triple> tripleSet) {
        double[] d = new double[tripleSet.size()];
        Triple[] t = new Triple[tripleSet.size()];
        Vector<Triple> tempTriple = new Vector<Triple>();
        int index = 0;
        for (int i = 0; i < tripleSet.size(); i++) {
            if (!tripleSet.get(i).getPredicate().getURI().equals(RDF.type.toString())) {
                d[index] = tvModel.getWeightModel().getWeight(this.model.createResource(tripleSet.get(i).getPredicate().getURI()));
            } else {                
                d[index] = tvModel.getWeightModel().getWeight(tripleSet.get(i));
            }
            t[index] = tripleSet.get(i);
            index++;
        }
        for (int i = 0; i < d.length - 1; i++) {
            double temp;
            Triple tempv;
            for (int j = 1; j < d.length; j++) {
                if (d[j - 1] < d[j]) {
                    temp = d[j - 1];
                    d[j - 1] = d[j];
                    d[j] = temp;

                    tempv = t[j - 1];
                    t[j - 1] = t[j];
                    t[j] = tempv;
                }
            }
        }

        tripleSet.clear();
        for (int i = 0; i < t.length; i++) {
            Triple tVector = t[i];
            double weight = d[i];
            if (weight == 1) {
                tempTriple.add(tVector);
            } else {
                tripleSet.add(tVector);
            }
        }
        tripleSet.addAll(tempTriple);

    }

    /**
     * ShortestPath를 이루는 MArc의 Weight sum을 Score로 반환
     *
     * @param sp ShortestPath
     * @return Sum of MArc weight
     */
    private double calculateShortestPathScore(ShortestPath sp) {
        double score = 0;
        for (int j = 0; j < sp.size(); j++) {
            double marcWeight = sp.get(j).getWeight();
            score += score + marcWeight;
        }
        return score;
    }

    /**
     * ShortestPathVector의 Score 계산
     *
     * @param spv
     * @return
     */
    private double calculateShortestPathVectorScore(ShortestPathVector spv) {
        double score = 0;
        Enumeration<ConceptArc> e = spv.keys();
        while (e.hasMoreElements()) {
            ConceptArc ca = e.nextElement();
            ShortestPath sp = spv.get(ca);
            score += this.calculateShortestPathScore(sp);
        }
        return score;
    }

    private void initializeNodeNumber() {
        this.v_num = 0;
    }

    public Vector<ConceptualGraph> getConceptualGraphSet() {
        return this.conceptualGraphSet;
    }

    public Vector<ConceptualGraph> getSTForAllCGSet() {
        return this.stForAllCGSet;
    }
}
